package vacuum.lgadmin.punishment;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;

import org.bukkit.ChatColor;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;

import vacuum.lgadmin.LGAdmin;
import vacuum.lgadmin.LGPlugin;

public class PunishmentPlugin extends LGPlugin
{
	//TODO convert commands to new system
	public PunishmentPlugin(LGAdmin lga)
	{
		super(lga);
	}

	private ForgiveThread muteThread;
	private ForgiveThread banThread;

	Punishment[] muted;
	Punishment[] banned;

	ArrayList<String> commands = new ArrayList<String>();

	NumberFormat nf = NumberFormat.getInstance();

	//Used for ZerothIndex's /tell commands
	public HashMap<CommandSender, CommandSender> replies = new HashMap<CommandSender, CommandSender>();


	@Override
	public void onDisable()
	{
		muteThread.halt();
		banThread.halt();
		try{
			writeToFile("muted", muted);
			writeToFile("banned", banned);
		} catch (IOException ex){
			ex.printStackTrace();
		}

		lga.getCommand("mute").setExecutor(null);
		lga.getCommand("unmute").setExecutor(null);
		lga.getCommand("ban").setExecutor(null);
		lga.getCommand("unban").setExecutor(null);
	}

	@Override
	public void onEnable()
	{

		log("Starting LGPunishment by FalseVacuum");

		log("Finding files");
		/* initialize files */
		try {
			File mutedFiles = new File(getDataFolder() + File.separator + "muted.txt");
			if(!mutedFiles.exists()){
				mutedFiles.getParentFile().mkdirs();
				mutedFiles.createNewFile();
			}

			File bannedFiles = new File(getDataFolder() + File.separator + "banned.txt");
			if(!bannedFiles.exists()){
				bannedFiles.getParentFile().mkdirs();
				bannedFiles.createNewFile();
			}

			File configFiles = new File(getDataFolder() + File.separator + "config.yml");
			if(!configFiles.exists()){
				configFiles.getParentFile().mkdirs();
				configFiles.createNewFile();
				
			}
		} catch (Exception ex){
			ex.printStackTrace();
		}

		commands = new ArrayList<String>();
		for(String s : getConfig().getString("mute.blockedcommands", "").toLowerCase().replaceAll(" ", "").split(","))
			commands.add(s);

		log("Loading mute lists.");
		/* find muted/banned players */
		try
		{
			muted = getPlayers("muted");
			banned = getPlayers("banned");
		} catch (FileNotFoundException e)
		{
			e.printStackTrace();
		}

		log("Registering commands.");
		/* register commands */
		lga.getCommand("mute").setExecutor(new CommandExecutor()
		{

			@Override
			public boolean onCommand(CommandSender sender, Command cmd, String label,
					String[] args)
			{
				if(args.length == 0)
					return false;
				Player player = lga.getServer().getPlayer(args[0]);
				if(player != null){
					args[0] = player.getName();
				}
				String message = " been muted for ";
				if(args.length == 1){
					mutePlayer(args[0], -1L);
					message += " an indefinite period of time.";
				} else if(args.length == 2){
					if(args[1].startsWith("-1"))
						return false;
					char type = args[1].charAt(args[1].length() - 1);
					long time = Long.parseLong(args[1].substring(0, args[1].length() - 1));
					message += time;
					switch (type)
					{
					case 'D':
					case 'd':
						message += " days.";
						time *= 864000;
						break;
					case 'S':
					case 's':
						message += " seconds.";
						time *= 1000;
						break;
					case 'M':
					case 'm':
						message += " minutes.";
						time *= 60000;
						break;
					case 'H':
					case 'h':
						message += " hours.";
						time *= 360000;
						break;
					default:
						log("Error! Time argument " + type + " not recognized. Accepted types are: s, m, d, h");
						return true;
					}
					mutePlayer(args[0], System.currentTimeMillis() + time);
				}
				if(player != null)
					player.sendMessage("You have" + message);
				lga.getServer().broadcast("message", Server.BROADCAST_CHANNEL_USERS);

				sender.sendMessage("Muted " + args[0]);
				return true;
			}
		});

		lga.getCommand("unmute").setExecutor(new CommandExecutor()
		{

			@Override
			public boolean onCommand(CommandSender sender, Command cmd, String label,
					String[] args)
			{
				if(args.length != 1)
					return false;

				Player player = lga.getServer().getPlayer(args[0]);
				if(player != null){
					args[0] = player.getName();
				}

				forgive(args[0], false);

				sender.sendMessage("Unmuted " + args[0]);
				return true;
			}
		});

		lga.getCommand("ban").setExecutor(new CommandExecutor()
		{

			@Override
			public boolean onCommand(CommandSender sender, Command cmd, String label,
					String[] args)
			{
				if(args.length == 0)
					return false;

				Player player = lga.getServer().getPlayer(args[0]);
				if(player != null){
					args[0] = player.getName();
				}

				String message = "You were banned from the server";

				if(args.length == 1){
					banPlayer(args[0], -1L);
					message += ".";
				}
				else if(args.length == 2){
					if(args[1].startsWith("-1"))
						return false;
					char type = args[1].charAt(args[1].length() - 1);
					long time = Long.parseLong(args[1].substring(0, args[1].length() - 1));
					message += " for " + time;
					switch (type)
					{
					case 'D':
					case 'd':
						time *= 864000;
						message += " days.";
						break;
					case 'S':
					case 's':
						time *= 1000;
						message += " seconds.";
						break;
					case 'M':
					case 'm':
						time *= 60000;
						message += " minutes.";
						break;
					case 'H':
					case 'h':
						time *= 360000;
						message += " hours";
						break;
					default:
						log("Error! Time argument " + type + " not recognized. Accepted types are: s, m, d, h");
						return false;
					}
					banPlayer(args[0], System.currentTimeMillis() + time);
				} else {
					return false;
				}
				if(player != null)
					player.kickPlayer(message);
				sender.sendMessage("Banned " + args[0]);
				return true;
			}
		});

		lga.getCommand("unban").setExecutor(new CommandExecutor()
		{

			@Override
			public boolean onCommand(CommandSender sender, Command cmd, String label,
					String[] args)
			{
				if(args.length != 1)
					return false;

				Player player = lga.getServer().getPlayer(args[0]);
				if(player != null){
					args[0] = player.getName();
				}

				forgive(args[0], true);
				sender.sendMessage("Unbanned " + args[0]);
				return true;
			}
		});

		/* Register listeners */

		log("Registering events.");

		PunishmentPlayerListener listen = new PunishmentPlayerListener(this);

		lga.getServer().getPluginManager().registerEvents(listen, lga);

		/* Start threads */

		log("Starting threads.");

		muteThread = new ForgiveThread(this, false);
		banThread = new ForgiveThread(this, true);

		log(this.getName() + "v" + this.getVersion() + " loaded.");

		/* implement ZerothIndex's /tell, /r commands */
		lga.getServer().getPluginManager().registerEvents(
				new Listener() {

			@EventHandler(priority=EventPriority.NORMAL)
			public void onPlayerQuit(PlayerQuitEvent event) {
				if (replies.containsKey(event.getPlayer())) {
					replies.remove(event.getPlayer());
				}
			}
		},
		lga);

		lga.getCommand("tell").setExecutor(new CommandExecutor()
		{

			@Override
			public boolean onCommand(CommandSender sender, Command command,
					String label, String[] args)
			{
				if(index(muted, sender.getName()) != -1){
					sender.sendMessage("You do not have access to this command while muted.");
					return true;
				}
				if (args.length > 1) {
					CommandSender recipient = lga.getServer().getPlayer(args[0]);
					if (recipient == null) {
						sender.sendMessage(ChatColor.RED+"Player "+args[0]+" not found.");
						return false;
					}
					String msg = "";
					for (int i = 1; i < args.length; ++i) {
						msg += args[i] + " ";
					}
					sender.sendMessage(ChatColor.GRAY+"You whisper to "+recipient.getName()+": "+msg);
					recipient.sendMessage(ChatColor.GRAY+sender.getName()+" whispers to you: "+msg);
					log(sender.getName()+" whispered to "+recipient.getName()+": "+msg);
					replies.put(sender, recipient);
					replies.put(recipient, sender);
					return true;
				} else {
					sender.sendMessage(ChatColor.RED+"Usage: /tell [player] [message]");
					return false;
				}
			}

		});

		lga.getCommand("r").setExecutor(new CommandExecutor()
		{

			@Override
			public boolean onCommand(CommandSender sender, Command command,
					String label, String[] args)
			{
				if(index(muted, sender.getName()) != -1){
					sender.sendMessage("You do not have access to this command while muted.");
					return true;
				}
				if (args.length > 0) {
					if (replies.containsKey(sender)) {
						CommandSender recipient = replies.get(sender);
						if (recipient instanceof Player && !((Player)recipient).isOnline()) {
							sender.sendMessage(ChatColor.RED+"Recipient is not online.");
							replies.remove(sender);
							return false;
						}
						String msg = "";
						for (int i = 0; i < args.length; ++i) {
							msg += args[i] + " ";
						}
						sender.sendMessage(ChatColor.GRAY+"You whisper to "+recipient.getName()+": "+msg);
						recipient.sendMessage(ChatColor.GRAY+sender.getName()+" whispers to you: "+msg);
						log(sender.getName()+" whispered to "+recipient.getName()+": "+msg);
						replies.put(recipient, sender);
						return true;
					} else {
						sender.sendMessage(ChatColor.RED+"No one to reply to.");
						return false;
					}
				} else {
					sender.sendMessage(ChatColor.RED+"Usage: /r [message]");
					return false;
				}
			}

		});

	}

	public Punishment[] getPlayers(String type) throws FileNotFoundException{
		Scanner s = new Scanner(new File(getDataFolder() + File.separator + type + ".txt"));
		ArrayList<Punishment> al = new ArrayList<Punishment>();
		while(s.hasNext()){
			String line = s.nextLine();
			Punishment p = new Punishment(line.substring(0, line.indexOf('|')), Long.valueOf(line.substring(line.indexOf('|') + 1, line.length())));

			/* The server's been off for a while, let's make sure everyone who should have been forgiven is forgiven */
			if(!p.isForgiven()){
				log("Adding " + p.name + " to the list of " + type + " people");
				al.add(p);
			} else {
				log(p.name + " has been un" + type);
			}
		}
		Punishment[] ar = al.toArray(new Punishment[al.size()]);
		bubblesort(ar);
		return ar;
	}

	public void mutePlayer(String player, long time){
		Punishment p = new Punishment(player, time);
		muted = insert(muted, p);
		try
		{
			writeToFile("muted", muted);
		} catch (IOException e)
		{
			e.printStackTrace();
		}

		if(time > 0 && muteThread.getNextTime() > time){
			muteThread.setNext(p);
		}
	}

	private void banPlayer(String player, long time){
		Punishment p = new Punishment(player, time);
		banned = insert(banned, p);
		try
		{
			writeToFile("banned", banned);
		} catch (IOException e)
		{
			e.printStackTrace();
		}

		if(time > 0 && banThread.getNextTime() > time){
			banThread.setNext(p);
		}
	}

	private Punishment[] insert(Punishment[] l, Punishment p){

		for(Punishment x : l){
			/* already contains */
			if(x.name.equals(p)){
				x.forgive = p.forgive;
				return l;
			}
		}

		/* expand array */
		Punishment[] ret = new Punishment[l.length + 1];
		System.arraycopy(l, 0, ret, 0, l.length);
		ret[l.length] = p;

		bubblesort(ret);

		return ret;
	}

	private Punishment[] remove(Punishment[] l, String name){
		int idx = index(l, name);
		if(idx == -1){
			return l;
		} else {
			Punishment[] ret = new Punishment[l.length - 1];
			System.arraycopy(l, 0, ret, 0, idx);
			System.arraycopy(l, idx + 1, ret, idx, ret.length - idx);
			return ret;
		}
	}

	private void writeToFile(String type, Punishment[] l) throws IOException {
		log("Saving data for " + type + ".txt...");
		FileWriter fw = new FileWriter(new File(getDataFolder() + File.separator + type + ".txt"), false);
		for(Punishment p : l){
			fw.write(p.name + "|" + p.forgive + "\n");
		}
		fw.flush();
		fw.close();
		log("Data saved!");
	}

	private void bubblesort(Punishment[] p){
		boolean b = true;
		while(b){
			b = false;
			for(int i = 1; i < p.length; i++){
				if(p[i - 1].name.compareTo(p[i].name) > 0){
					Punishment temp = p[i];
					p[i] = p[i - 1];
					p[i - 1] = temp;
					b = true;
				}
			}
		}
	}

	static int index(Punishment[] l, String player){

		player = player.toLowerCase();

		if(l.length == 0)
			return -1;

		/* binary search */
		int idx = l.length / 2;
		int diff = l.length / 4;
		while(diff > 0){
			int c = l[idx].name.toLowerCase().compareTo(player);
			if(c > 0){
				idx -= diff;
			} else if (c < 0){
				idx += diff;
			} else {
				return idx;
			}
			idx /= 2;
		}
		return (l[idx].name.equalsIgnoreCase(player)) ? idx : -1;
	}

	public void forgive(String player, boolean type)
	{
		Punishment[] list = remove(type ? banned : muted, player);
		log("Forgiving " + player + " for their " + (type ? "ban" : "mute"));
		if(type)
			banned = list;
		else
			muted = list;
		try
		{
			writeToFile(type ? "banned" : "muted", list);
		} catch (IOException e)
		{
			e.printStackTrace();
		}
		Player p;
		if(!type && (p = lga.getServer().getPlayer(player)) != null){
			p.sendMessage("You have been unmuted.");
		}
	}

	public Punishment getNextForgive(boolean type)
	{
		Punishment[] l = type ? banned : muted;

		if(l.length == 0){
			return null;
		}

		Punishment next = l[0];

		for (Punishment p : l)
		{
			if(p.forgive > 0 && p.forgive < next.forgive)
				next = p;
		}

		return (next.forgive > 0) ? next : null;
	}

	@Override
	public String getName()
	{
		return "LGPunishment";
	}

	@Override
	public String getVersion()
	{
		return "1.1.3";
	}

}
